﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using Hiro.ActivationPoints.Interfaces;
using Hiro.Interfaces;
using Mono.Cecil;
using Mono.Cecil.Cil;

namespace Hiro.ActivationPoints
{
    /// <summary>
    /// Represents an activation point that is used whenever a constructor needs to be invoked.
    /// </summary>
    public class ConstructorActivationPoint : IConstructorActivationPoint
    {
        /// <summary>
        /// The target constructor.
        /// </summary>
        private readonly ConstructorInfo _constructor;

        /// <summary>
        /// The resolver that will be used to determine which services should be used to 
        /// initialize the constructor arguments.
        /// </summary>
        private readonly IArgumentResolver _resolver;

        /// <summary>
        /// Initializes a new instance of the ConstructorActivationPoint class.
        /// </summary>
        /// <param name="constructor">The target constructor.</param>
        /// <param name="argumentResolver">The resolver that will determine which dependencies will be used to initialize the constructor parameters.</param>
        public ConstructorActivationPoint(ConstructorInfo constructor, IArgumentResolver argumentResolver)
        {
            _constructor = constructor;
            _resolver = argumentResolver;
        }

        /// <summary>
        /// Gets the value indicating the target type that holds the target constructor.
        /// </summary>
        /// <value>The declaring type of the target constructor.</value>
        public Type TargetType
        {
            get { return _constructor.DeclaringType; }
        }

        /// <summary>
        /// Gets the value indicating the constructor that will be used with this activation point.
        /// </summary>
        /// <value>The constructor that will be used to instantiate the target type.</value>
        public ConstructorInfo TargetConstructor
        {
            get { return _constructor; }
        }

        /// <summary>
        /// Emits the necessary <paramref name="IL"/> instructions to instantiate the current type.
        /// </summary>
        /// <param name="targetMethod">The method that will instantiate the current type.</param>
        /// <param name="IL">The <see cref="CilWorker"/> that points to the method body of the method that will instantiate the current type.</param>
        /// <param name="hostModule">The module that contains the container type.</param>
        /// <param name="dependencyMap">The dependency map that describes the list of dependencies in the current application.</param>
        public void Emit(MethodReference targetMethod, CilWorker IL, ModuleDefinition hostModule, IDependencyMap dependencyMap)
        {
            if (targetMethod == null)
                throw new ArgumentNullException("targetMethod");

            var declaringType = targetMethod.DeclaringType;

            var parameters = _constructor.GetParameters();
            foreach (var parameter in parameters)
            {
                // Determine which dependency should be mapped
                // to the target parameter
                var currentDependency = _resolver.Resolve(parameter, dependencyMap);

                // Emit the parameter value
                var activationPoint = dependencyMap.GetActivationPoint(currentDependency);
                activationPoint.Emit(targetMethod, IL, hostModule, dependencyMap);
                
                // Cast it to the parameter type
                var parameterType = hostModule.Import(parameter.ParameterType);
                IL.Emit(OpCodes.Isinst, parameterType);
            }

            var constructor = hostModule.Import(_constructor);

            // Create the new object
            IL.Emit(OpCodes.Newobj, constructor);
        }

        /// <summary>
        /// Determines whether or not the current <see cref="IDependencyMap"/> has the necessary
        /// dependencies for the current activation point to emit the necessary IL to instantiate the current type.
        /// </summary>
        /// <param name="dependencyContainer">The target dependency map.</param>
        /// <returns><c>true</c> if all required dependencies can be instantiated; otherwise, it will return <c>false</c>.</returns>
        public bool IsValidWith(IDependencyContainer dependencyContainer)
        {
            // Every parameter on the target constructor must be
            // resolvable
            bool result = true;
            foreach (var param in _constructor.GetParameters())
            {
                var dependency = _resolver.Resolve(param, dependencyContainer);
                if (dependency != null && dependencyContainer.CanResolve(dependency))
                    continue;

                result = false;
                break;
            }

            return result;
        }
    }
}
